﻿using System;
using System.Collections.Generic;
using System.Text;

namespace GA
{
    /// <summary>
    /// Genetic algorithm population class.
    /// </summary>
    /// <remarks>Written by Kerem KAT, keremkat, gmail, com</remarks>
    public class Population
    {
        private Random x = new Random();
        public List<Chromosome> pop;
        public Chromosome bestSoFar;

        // ex: m.11 c.99 e.07 dee50
        public double mutationRate;
        public double crossOverRate;
        public double elitism;
        public int delayElitismEpochs;

        public int N;
        public int size;
        public int generation = 1;

        public Population(int Size, int n, double mutationRate, double crossOverRate, double elitism, int delayElitismEpochs)
        {
            N = n;
            size = Size;
            pop = new List<Chromosome>(size);

            this.mutationRate = mutationRate;
            this.crossOverRate = crossOverRate;
            this.elitism = elitism;
            this.delayElitismEpochs = delayElitismEpochs;

            for (int i = 0; i < size; ++i)
                pop.Add(new Chromosome(x.Next(), N));
            bestSoFar = new Chromosome(N);
        }

        public int Fitness()
        {
            int fit = 0;
            foreach (Chromosome c in pop)
                fit += c.Fitness;
            return fit;
        }

        public int MaxFitness()
        {
            int max = int.MinValue;
            foreach (Chromosome c in pop)
                if (max < c.Fitness)
                    max = c.Fitness;
            return max;
        }

        public int MinFitness()
        {
            int min = int.MaxValue;
            foreach (Chromosome c in pop)
                if (min > c.Fitness)
                    min = c.Fitness;
            return min;
        }

        public void NextGeneration()
        {
            Reproduce();
            CrossOver();
            Mutate();
            SortPopulation(); // for elitism

            if (pop[0].Fitness < bestSoFar.Fitness)
                bestSoFar = pop[0];
        }

        public int ChromoCompare(Chromosome c1, Chromosome c2)
        {
            return c1.Fitness - c2.Fitness;
        }


        public void SortPopulation()
        {
            pop.Sort(ChromoCompare);
        }

        public void Mutate()
        {
            Random x = new Random();
            if (x.NextDouble() > mutationRate)
                return;
            pop[x.Next(pop.Count)].Mutate();
        }

        public void Reproduce()
        {
            int cumFit = 0;
            int rnd;
            int fit = Fitness();
            int maxFit = MaxFitness();
            bool done = false;
            Random x = new Random();

            List<Chromosome> newGeneration = new List<Chromosome>(pop.ToArray());
            newGeneration.RemoveRange((int)(size * elitism), (int)(size * (1 - elitism)));

            //for (int i = 0; i < size * elitism; ++i)
            //  newGeneration.Add(pop[i]);

            int startIndex = (int)(size * elitism);
            if (delayElitismEpochs > generation)
            {
                newGeneration.Clear();
                startIndex = 0;
            }

            for (int i = startIndex; i < size; ++i)
            {
                rnd = x.Next(fit);
                done = false;
                foreach (Chromosome c in pop)
                {
                    cumFit += maxFit - c.Fitness;
                    if (cumFit >= rnd)
                    {
                        newGeneration.Add(c);
                        done = true;
                        break;
                    }
                }
                if (!done)
                    newGeneration.Add(new Chromosome(N));
                cumFit = 0;
            }
            pop = newGeneration;
            generation++;
        }

        public void CrossOver()
        {
            Random x = new Random();
            List<Chromosome> newPop = new List<Chromosome>(size);
            int k = 0;
            for (int i = 0; i < pop.Count; ++i)
            {
                if (x.NextDouble() <= crossOverRate)
                    newPop.Add(Chromosome.CrossOver(pop[x.Next(pop.Count)], pop[x.Next(pop.Count)]));
                else
                    newPop.Add(pop[k++]);
            }
            pop = newPop;
        }

        public string ToOneLineString()
        {
            return string.Format(
                "N: {0}, popSize: {1}, gen: {2}, mut: {3:N2}, xover: {4:N2}, elit: {5:N2}, bestSoFar: {6:N2}",
                N, size, generation, mutationRate, crossOverRate, elitism, bestSoFar.Fitness
                );
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (Chromosome c in pop)
            {
                sb.Append(c.Fitness.ToString().PadRight(3));
                sb.Append("- ");
                sb.AppendLine(c.ToString());
            }
            sb.AppendLine("Generation: " + generation);
            sb.AppendLine("Total Fitness: " + Fitness());
            sb.AppendLine("Average Fitness: " + Fitness() * 1.0 / pop.Count);
            sb.AppendLine("BestSoFar: " + bestSoFar.ToString());
            sb.AppendLine("Fitness: " + bestSoFar.Fitness);
            return sb.ToString();
        }

    }
}
